function ws(subData, closeCallback, site) {

	var subData = subData;
	var closeCallback = closeCallback;
	var connectState = 0; // 0 : disconnected 1 : connected 2 : connecting
	var websocket;
	var siteFlag = site;
	var cnUrl = if_offline == 1 ? "wss://real.okcoin.cn:10440/websocket" : "ws://real.okcoin.cn:10440/websocket";
	var comUrl = if_offline == 1 ? "wss://real.okcoin.com:10440/websocket" : "ws://real.okcoin.com:10440/websocket";
	var sub = 'sub';
	var binary = 0;

	var constants = {
		okLogin: "login",
		okLogout: "logout"
	};
	var personalType = ["ok_sub_spotcny_trades","ok_sub_spotusd_trades", "ok_sub_spot_userinfo",];

	this.tryConnect = function () {

		if (!window.WebSocket) { //不支持websocket 启动轮训
			setInterval(closeCallback, 2000);
			return;
		}

		setInterval(function () {

			if (connectState == 1) { // 已经连接
				return;
			}
			if (connectState == 2) { // 正在连接
				closeCallback();
				return;
			}
			console.info("try connect...");
			closeCallback();
			init();
		}, 2000);
	};

	this.isConnection = function () {
		return connectState == 1;
	};

	function init() {
		connectState = 2;
		if (siteFlag == 2) {
			websocket = new WebSocket(comUrl);
		} else {
			websocket = new WebSocket(cnUrl);
		}

		websocket.onopen = function (evt) {
			connectState = 1;
			checkConnection();
			onOpen(evt);
		};
		websocket.onclose = function (evt) {
			connectState = 0;
			onClose(evt);
		};
		websocket.onmessage = function (evt) {
			onMessage(evt);
		};
		websocket.onerror = function (evt) {
			connectState = 0;
			onError(evt);
		};
	}

	function send(msg) {
		websocket.send(msg);
	}

	function onOpen(evt) {
		console.info("connected");
		subscribeAllData();
	}

	function onClose(evt) {
		//console.info('lost connection...');
	}

	function onMessage(evt) {
		dealData(evt.data);
	}

	function onError() {
		//console.info("error occurred...");
	}

	function subscribeAllData() {
		var sub = subData;
		if (!sub) {
			return;
		}

		for (var key in sub) {
			if (subData[key] == undefined) {
				continue;
			}
			if (key == constants.okLogin) {
				login(sub[key]);
				continue;
			}
			if (key == constants.okLogout) {
				logout();
				continue;
			}
			if (key.indexOf('ok_sub') == -1) {
				continue;
			}
			var found = false;
			for (var i = 0; i < personalType.length; ++i) {
				if (personalType[i] == key) {
					found = true;
					break;
				}
			}
			if (found) {
				continue;
			}
			subscribe(key);
		}

	}

	function dealData(data) {
		if (!data || !subData) {
			return;
		}
		var jsons = JSON.parse(data);
		if (jsons instanceof Array) {
			for (var i in jsons) {
				dealSingleData(jsons[i]);
			}
		} else {
			dealSingleData(jsons);
		}

	}
	function dealSingleData(data) {
		var sub = subData;
		for (var key in sub) {
			if (sub[key] == undefined) {
				continue;
			}
			if (key == constants.okLogin
				|| key == constants.okLogout) {
				continue;
			}
			if (data.channel == key) {
				sub[key](data.data);
			}
		}
	}

	function checkConnection() {
		setTimeout(function () {
			if (connectState == 1) {
				ping();
				checkConnection();
			}
		}, 5000);
	}

	function addSubType(type, callback) {
		subData[type] =  callback;
		subscribe(type);
	}

	function subscribe(key) {
		send('{"event": "addChannel", "channel": "' + key + '", "binary": "' + binary + '"}');
	}

	function removeSubType(type) {
		subData[type] = undefined;
		send('{"event": "removeChannel", "channel": "' + type + '"}');
	}

	this.addSub = function(type, callback) {
		addSubType(type, callback);
	};

	this.rmSub = function (type) {
		removeSubType(type);
	};

	//*********************** common *********************************************
	function login(userId) {
		send('{"event": "' + constants.okLogin + '", "userId": "' + userId + '"}');
	}

	function logout() {
		send('{"event": "' + constants.okLogout + '"}');
	}

	function ping() {
		send('{"event": "ping"}');
	}

	//*********************** common *********************************************
}


function WebSocketUtils() {
	var constants = {
		btc5DepthCn : ['ok_sub_spot_btc_depth_5', 'ok_sub_spot_btc_depth_5_merge_1.0',
			'ok_sub_spot_btc_depth_5_merge_0.1'],
		btc5DepthCom: ['ok_sub_spot_btc_depth_5', 'ok_sub_spot_btc_depth_5_merge_0.2'],
		ltc5DepthCn : ['ok_sub_spot_ltc_depth_5', 'ok_sub_spot_ltc_depth_5_merge_0.1'],
		ltc5DepthCom: ['ok_sub_spot_ltc_depth_5', 'ok_sub_spot_ltc_depth_5_merge_0.01'],
		btc200DepthCn : ['ok_sub_spot_btc_depth', 'ok_sub_spot_btc_depth_merge_1.0',
			'ok_sub_spot_btc_depth_merge_0.1'],
		btc200DepthCom: ['ok_sub_spot_btc_depth', 'ok_sub_spot_btc_depth_merge_0.2'],
		ltc200DepthCn : ['ok_sub_spot_ltc_depth', 'ok_sub_spot_ltc_depth_merge_0.1'],
		ltc200DepthCom: ['ok_sub_spot_ltc_depth', 'ok_sub_spot_ltc_depth_merge_0.01']
	};
	var depth5Cn = [constants.btc5DepthCn, constants.ltc5DepthCn];
	var depth5Com = [constants.btc5DepthCom, constants.ltc5DepthCom];
	var depth200Cn = [constants.btc200DepthCn, constants.ltc200DepthCn];
	var depth200Com = [constants.btc200DepthCom, constants.ltc200DepthCom];
	var depth5 = [0, depth5Cn, depth5Com];
	var depth200 = [0, depth200Cn, depth200Com];
	this.getSubSpotTicker = function (symbol) {
		if (symbol == 0) {
			return "ok_sub_spot_btc_ticker";
		} else {
			return "ok_sub_spot_ltc_ticker";
		}
	};
	
	this.getSubSpot5Depth = function (symbol, isMerge, siteFlag) {
		if (symbol != 0 && symbol != 1) {
			return depth5[siteFlag][0][0];
		}
		if (isMerge != 1 && isMerge != 2) {
			return depth5[siteFlag][symbol][0];
		}
		return depth5[siteFlag][symbol][isMerge];
	};
	
	this.getSubSpot200Depth = function (symbol, isMerge, siteFlag) {
		if (symbol != 0 && symbol != 1) {
			return depth200[siteFlag][0][0];
		}
		if (isMerge != 0 && isMerge != 1 && isMerge != 2) {
			return depth200[siteFlag][symbol][0];
		}
		return depth200[siteFlag][symbol][isMerge];
	};
	
	this.getSubSpotTrade = function (symbol) {
		var res = "ok_sub_spot_";
		if (symbol == 0) {
			res += "btc_trades";
		} else {
			res += "ltc_trades";
		}
	};
	this.getSpotPersonalOrderInfo = function (symbol) {
		if (symbol == 0) {
            return  'ok_sub_spot'+(site_flag==2?'usd':'cny')+'_trades';
		} else {
            return 'ok_sub_spot'+(site_flag==2?'usd':'cny')+'_trades';
		}
	};
	return this;
}

var WsUtils = WebSocketUtils();

Date.prototype.Format = function(fmt) {
	var o = {
		"M+" : this.getMonth()+1,                 //月份
		"d+" : this.getDate(),                    //日
		"h+" : this.getHours(),                   //小时
		"m+" : this.getMinutes(),                 //分
		"s+" : this.getSeconds(),                 //秒
		"q+" : Math.floor((this.getMonth()+3)/3), //季度
		"S"  : this.getMilliseconds()             //毫秒
	};
	if(/(y+)/.test(fmt))
		fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
	for(var k in o)
		if(new RegExp("("+ k +")").test(fmt))
			fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
	return fmt;
};
